#include "Precomputation.h"

#include "Sort.h"

#include <cmath>
#include <cstdlib> 
#include <fstream>
#include <iostream>
#include <sstream>


void explode(Strings& p_results,
			 const std::string& p_string,
			 const std::string& p_seperator)
{
	std::string temp = p_string;
	size_t found = temp.find(p_seperator);
	while (found != std::string::npos)
	{
		if (found > 0)
		{
			p_results.push_back(temp.substr(0,found));
		}
		temp = temp.substr(found + p_seperator.length());
		found = temp.find(p_seperator);
	}
	if (temp.length() > 0)
	{
		p_results.push_back(temp);
	}
}

bool getFileLines(Lines& p_lines, const char* p_filename)
{
	std::ifstream filestream(p_filename, std::ios::binary);
	
	if(filestream.fail())
		std::cout << "Failed to open: " << p_filename;

	// Get file length.
	filestream.seekg (0, std::ios::end);
	unsigned int length = static_cast<unsigned int>(filestream.tellg());
	filestream.seekg (0, std::ios::beg);

	char* data = new char[length];
	filestream.read(data, length);

	unsigned int start = 0;
	bool comment = false;
	unsigned int i = 0;
	for (; i < length; ++i)
	{
		char letter = data[i];
		if (i == start && letter == '#')
		{
			comment = true;
		}
		if (letter == '\n' || letter == '\r')
		{
			if (comment == false && i != start)
			{
				if (start + (i - start) > length)
					std::cout << "Index out of range.";
				p_lines.push_back(std::string(data, start, i - start));
			}
			comment = false;
			start = i + 1;
		}
	}
	if (start < length && comment == false)
	{
		if (length != (start + (i - start)))
			std::cout << "Index out of range.";
		std::string line = std::string(data, start, i - start);
		p_lines.push_back(line);
	}
	
	delete[] data;

	return true;
}

template<typename T>
T parse(const std::string& p_string)
{
	std::stringstream ss(p_string);
	T number;
	ss >> number;
	return number;
}

Precomputation::Precomputation()
{
}


Precomputation::~Precomputation()
{
}

void Precomputation::calculateIntersections(const char* p_datafile, 
											unsigned int*& p_intersections, 
											Strings& p_attributes)
{
	Lines lines;
	getFileLines(lines, p_datafile);

	explode(p_attributes, lines[0], "\t");

	unsigned int attributeCount = p_attributes.size();

	p_intersections = new unsigned int[attributeCount * attributeCount];

	// set diagonals to zero
	for (unsigned int i = 0; i < attributeCount; ++i)
	{
		p_intersections[i * attributeCount + i] = 0;
	}

	unsigned int elementCount = lines.size() - 1;

	float* values = new float[attributeCount * elementCount];

	for (unsigned int i = 1; i < elementCount + 1; ++i)
	{
		Strings stringValues;
		explode(stringValues, lines[i], "\t");
		if (stringValues.size() - 1 != attributeCount)
		{
			std::cout << "Warning, invalid attribute count.\tFile: " << p_datafile << "\tLine: " << i << std::endl;
		}

		for (unsigned int j = 1; j < stringValues.size(); ++j)
		{
			float value = parse<float>(stringValues[j]);

			values[(i - 1) + (j - 1) * elementCount] = value;
		}
	}	

	// Calculate intersections
	for (unsigned int i = 0; i < attributeCount - 1; ++i)
	{
		for (unsigned int j = i + 1; j < attributeCount; ++j)
		{
			const unsigned int attribute1Offset = i * elementCount;
			const unsigned int attribute2Offset = j * elementCount;

			unsigned int intersectionCount = 0;

			for (unsigned int k = 0; k < elementCount - 1; ++k)
			{
				const float element1Start = values[attribute1Offset + k];
				const float element1End = values[attribute2Offset + k];

				for (unsigned int l = k + 1; l < elementCount; ++l)
				{
					const float element2Start = values[attribute1Offset + l];
					const float element2End = values[attribute2Offset + l];

					if ((element1Start - element2Start) * (element1End - element2End) < 0)
					{
						intersectionCount++;
					}
				} 
			}

			p_intersections[i * attributeCount + j] = intersectionCount;
			p_intersections[j * attributeCount + i] = intersectionCount;
		}
	}

}


void Precomputation::createMatlabCommands(const char* p_datafile, bool p_useHeuristic)
{
	std::ofstream fileUnsorted("MatlabUnsorted.txt");
	std::ofstream fileSorted("MatlabSorted.txt");

	
	Lines lines;
	getFileLines(lines, p_datafile);

	Strings attributes;

	explode(attributes, lines[0], "\t");

	unsigned int attributeCount = attributes.size();

	unsigned int elementCount = lines.size() - 1;

	float* values = new float[attributeCount * elementCount];

	// add attributes command
	fileUnsorted << "attributes={";
	for (unsigned int i = 0; i < attributeCount; ++i)
	{
		fileUnsorted << "\'" << attributes[i] << "\'";
		if (i < attributes.size() - 1)
			fileUnsorted << "; ";
	}
	fileUnsorted << "};\n";

	// add elements command
	fileUnsorted << "elements={";
	for (unsigned int i = 1; i < elementCount + 1; ++i)
	{
		Strings stringValues;
		explode(stringValues, lines[i], "\t");
		if (stringValues.size() - 1 != attributeCount)
		{
			std::cout << "Warning, invalid attribute count.\tFile: " << p_datafile << "\tLine: " << i << std::endl;
		}
		fileUnsorted << "\'" << stringValues[0] << "\'";
		if (i < elementCount)
			fileUnsorted << "; ";
	}
	fileUnsorted << "};" << std::endl;

	// add data command
	fileUnsorted << "data=[";
	for (unsigned int i = 1; i < elementCount + 1; ++i)
	{
		Strings stringValues;
		explode(stringValues, lines[i], "\t");
		if (stringValues.size() - 1 != attributeCount)
		{
			std::cout << "Warning, invalid attribute count.\tFile: " << p_datafile << "\tLine: " << i << std::endl;
		}

		for (unsigned int j = 1; j < stringValues.size(); ++j)
		{
			fileUnsorted << stringValues[j] << " ";
			float value = parse<float>(stringValues[j]);

			values[(i - 1) + (j - 1) * elementCount] = value;
		}

		fileUnsorted << "; ";
	}
	fileUnsorted << "];" << std::endl;
	fileUnsorted << "parallelcoords(data,\'group\',elements,\'labels\',attributes,\'Standardize\',\'on\');" << std::endl;
	
	unsigned int* intersections = new unsigned int[attributeCount * attributeCount];
	// Calculate intersections
	for (unsigned int i = 0; i < attributeCount - 1; ++i)
	{
		for (unsigned int j = i + 1; j < attributeCount; ++j)
		{
			const unsigned int attribute1Offset = i * elementCount;
			const unsigned int attribute2Offset = j * elementCount;

			unsigned int intersectionCount = 0;

			for (unsigned int k = 0; k < elementCount - 1; ++k)
			{
				const float element1Start = values[attribute1Offset + k];
				const float element1End = values[attribute2Offset + k];

				for (unsigned int l = k + 1; l < elementCount; ++l)
				{
					const float element2Start = values[attribute1Offset + l];
					const float element2End = values[attribute2Offset + l];

					if ((element1Start - element2Start) * (element1End - element2End) < 0)
					{
						intersectionCount++;
					}
				} 
			}

			intersections[i * attributeCount + j] = intersectionCount;
			intersections[j * attributeCount + i] = intersectionCount;
		}
	}

	unsigned int intersectionCount = 0;
	int* attributeOrder = 0;
	float time = 0;

	for (unsigned int i = 0; i < attributeCount-1; ++i)
	{
		intersectionCount += intersections[(i * attributeCount) + i + 1];
	}
	std::cout << "Intersection count before: " << intersectionCount << std::endl;

	if (p_useHeuristic)
	{
		Sort::heuristicSort(intersections, attributeCount, intersectionCount, attributeOrder, time);
	}
	else
	{
		Sort::bruteForceSortDynamic(intersections, attributeCount, intersectionCount, attributeOrder, time);
	}

	std::cout << "intersection count after: " << intersectionCount << std::endl;

	// add attributes command
	fileSorted << "attributes={";
	for (unsigned int i = 0; i < attributeCount; ++i)
	{
		fileSorted << "\'" << attributes[attributeOrder[i]] << "\'";
		if (i < attributes.size() - 1)
			fileSorted << "; ";
	}
	fileSorted << "};\n";

	// add elements command
	fileSorted << "elements={";
	for (unsigned int i = 1; i < elementCount + 1; ++i)
	{
		Strings stringValues;
		explode(stringValues, lines[i], "\t");
		if (stringValues.size() - 1 != attributeCount)
		{
			std::cout << "Warning, invalid attribute count.\tFile: " << p_datafile << "\tLine: " << i << std::endl;
		}
		fileSorted << "\'" << stringValues[0] << "\'";
		if (i < elementCount)
			fileSorted << "; ";
	}
	fileSorted << "};" << std::endl;

	// add data command
	fileSorted << "data=[";
	for (unsigned int i = 1; i < elementCount + 1; ++i)
	{
		Strings stringValues;
		explode(stringValues, lines[i], "\t");
		if (stringValues.size() - 1 != attributeCount)
		{
			std::cout << "Warning, invalid attribute count.\tFile: " << p_datafile << "\tLine: " << i << std::endl;
		}

		for (unsigned int j = 1; j < stringValues.size(); ++j)
		{
			fileSorted << stringValues[attributeOrder[j-1]+1] << " ";
		}

		fileSorted << "; ";
	}
	fileSorted << "];" << std::endl;
	fileSorted << "parallelcoords(data,\'group\',elements,\'labels\',attributes,\'Standardize\',\'on\');" << std::endl;

	delete[] intersections;
	delete[] attributeOrder;
}
